<!DOCTYPE html>
<!-- --------------------------------------- -->
<!-- Ganja.js playground and sample browser  -->
<!-- --------------------------------------- -->
<HEAD>
  <!-- mobile -->
  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, user-scalable=no">

  <!-- Global site tag (gtag.js) - Google Analytics -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=UA-96802693-2"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
  </script>
  
  <!-- Fonts and Icons -->
  <link rel="shortcut icon" type="image/x-icon" href="https://enkimute.github.io/ganja.js/images/favicon.ico">
  <link href="https://fonts.googleapis.com/css?family=Encode+Sans+Condensed" rel="stylesheet">
  
  <!-- Local Style -->
  <style>
    body           { background: #20262E; color: #FBFBFB; padding:0; margin:0; display:none; flex-direction:column; min-height:100vh; max-height:100vh; position:relative; overflow:hidden; }
    h1,h2,h3,h4    { font-size: 100%; font-weight: 600; }
    body           { font-size: 14px; font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; }
    .hider         { transition: all 0.5s; overflow:hidden; flex-shrink:0; }
    .header        { height:45px; flex-grow:0; flex-shrink:0; width:100%; display:flex; flex-direction:row; border-bottom:0px solid #2d333b; padding-left:5px; padding-right:5px; box-sizing:border-box; }
    .intro         { background: #0084FF; display: flex; flex-shrink:0; padding: 10px; transition:all 0.15s linear; height:100%; position:relative; width:100%; box-sizing:border-box; font-size:110%; }
    .intro h1      { font-size:120%; }
    .introBlock    { margin-right: 30px; flex-grow:1; }
    .intro a       { color: #FBFBFB; text-decoration:none; display:block; margin-bottom:5px;}
    .intro a:hover { color: #FFBBFF; }
    .introButton   { background: #FFFFFF; color: #20262E; padding: 5px 9px; border-radius:4px; display:inline-block; margin:5px; transition: all 0.15s linear; cursor:pointer; position:relative; user-select:none; box-sizing:border-box; border:2px solid rgba(0,0,0,0); }
    .introButton:hover { box-shadow: 0 4px 4px rgba(0,0,0,.3); top: -2px;  }
    .header .introButton:hover { box-shadow: 0 4px 4px rgba(0,0,0,.3); top: 0px; border:2px solid #F08000; }
    .introButton:active { top:0px; background:#20262E; color:#FFF }
    .introButton.active { animation: ani .5s infinite; }
    .content       { width:100%; height:100%; flex-grow:1; /*flex-wrap:wrap;*/ overflow:hidden; display:flex; box-sizing:border-box; max-width:100vw; position:relative;  }
    .gutter        { background: #2d333b; min-width:4px; min-height:100%; cursor:ew-resize;}
    .left          { width: 433px; box-sizing:border-box; flex-grow:0; flex-shrink:0; overflow:hidden; flex-flow:wrap; max-height:100%; position:relative; user-select:none; }
    .leftc         { width: 100%; box-sizing:border-box; flex-grow:0; flex-shrink:0; overflow:hidden; overflow-Y:auto; flex-flow:wrap; max-height:calc(100% - 35px); }
    .leftc::-webkit-scrollbar { width: 4px; height: 8px; border-radius:4px; background-color: #808080; }
    .leftc::-webkit-scrollbar-thumb { background: #fbfbfb; }
    .right         { width: 45vw; display:flex; box-sizing:border-box; flex-grow:0; flex-shrink:0; }
    .center        { box-sizing:border-box; flex-grow:1; overflow:hidden;   }
    .source        { margin: 0; flex-grow:1; box-sizing:border-box; width:100%; }
    .card          { display:block; position:relative; display:inline-block; width:120px; height:80px; text-align:center; cursor:pointer; background-color:#FBFBFB; color:#20262E; border-radius:10px; border:1px solid rgba(0,0,0,0.3); margin:5px; padding:3px;  background-position:center; background-size:cover; background-repeat:no-repeat; margin-bottom:35px; border:2px solid #8090A0; opacity:0.9; }
    .card:hover    { border:2px solid blue; opacity:1; }
    .card p        { position:absolute; bottom:-40px; color:#fbfbfb; white-space:nowrap; max-width:100%; overflow:hidden; }
    #sample        { width:100%; height:100%; max-height:100vh; overflow:hidden; background-color:#FBFBFB; }
    .sampleGroup   { width:100%; padding:5px; box-sizing:border-box; background-color:#404040; margin-left:0px; cursor:pointer; white-space:no-wrap; }
    .sampleGroup:hover { background-color:#FBFBFB; color:#404040; }
    .sampleGroup>span, .properties span { float:right; display:block; color:#a0a0a0; padding-right:5px; }
    .group         { margin-top:10px; }
    .selected      { background-color:#405080; }
    .properties    { flex-direction: column; position:relative; padding:5px; max-width:100%; display:none; } 
    .properties input, .properties textarea { background-color:#202020; color:#b0b0b0; border:1px solid #505050; padding:2px; max-width:100%; margin-bottom:5px; resize:none; }
    .inspect       { display:none; flex-direction: column; position:relative; padding:5px; max-width:100%; }
    .inspect #copyJSON { padding-top:10px; padding-bottom:10px; margin-bottom:10px; margin-top:10px; }
    .inspect #editJSON { position:fixed; top:-100px; left:-100px; width:100px; height:20px; overflow:hidden; }
    #inspectTable2 td  { font-size:80%; background-color:#20262e; text-align:center; padding-top:4px; padding-bottom:4px; }
    #inspectTable2 td.odd { background-color:#40464e; }
    #inspectTable2 td.min { background-color:#60262e; } 
    #inspectTable2 td.zer { background-color:#00602e; } 
    #inspectTable2 td.plu { background-color:#202660; } 
    #inspectTable2 small { float:right; color:#909090; }
    #inspectTable2 input { width:100%; background:#505050; border:1px solid rgba(255,255,255,0.4); color:#CCC; font-weight:100; font-size:130%; }
    .help          { display:none; flex-direction: column; position:relative; padding:5px; max-width:100%; }
    .dropzone      { height:50px; line-height:50px; text-align:center; background:#202020; color:#707070; margin:5px; border:1px dashed #505050; }
    #stash         { position:relative; width:calc(100% + 5px);  }
    .tabs          { flex-grow:0; flex-shrink:0; display:flex; flex-direction:row; width:100%; height:30px; border-bottom:2px solid #2d333b; }
    .tab           { flex-grow:0; line-height:30px; border-top-right-radius:35px; cursor:pointer; padding-left:10px; border:1px solid #707070; color:#707070; background:#202020; user-select:none; padding-right:20px; }
    .tab.active    { background:#405060; color:#fbfbfb; border:1px solid white; cursor:default; }
    .rtab          { flex-grow:0; line-height:30px; border-top-left-radius:35px; cursor:pointer; padding-left:20px; border:1px solid #707070; color:#707070; background:#202020; user-select:none; padding-right:10px; }
    .rtab.active   { background:#405060; color:#fbfbfb; border:1px solid white; cursor:default; }
    .delete        { position:absolute; top:0px; right:5px; font-size:20px }
    .delete:hover  { color:red; font-weight:1000; }
    .help table, .inspect table { border:1px solid rgba(255,255,255,0.1); }
    .help th,    .inspect th    { background:#cacaca; color:#20262e; }
    .help tr,    .inspect tr    { background:#30363e; }
    .help tr:hover, .inspect tr:hover { background:#40464e; }
    .help td, .inspect td    { border:0px; padding-right:4px; white-space:nowrap; overflow:hidden; }
    .help td { text-align:center; padding-top:3px; }
    @media only screen and (max-device-width: 600px) {
      .left { width : 150px; }
      .right { width : 100vw; }
    }
    @media only screen and (max-device-width: 1200px) {
      .left { width : 150px; }
    }
    @keyframes ani {
      0%   { background:#FFFFFF }
      50%  { background:#FFCCCC }
      100% { background:#FFFFFF }
    }
  </style>
  <!-- ganja -->
  <SCRIPT SRC="../ganja.js"></SCRIPT>
  <SCRIPT SRC="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js"></SCRIPT>
  <!-- katex math typesetting -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.css" integrity="sha384-9eLZqc9ds8eNjO3TmqPeYcDj8n+Qfa4nuSiGYa6DjLNcv9BtN69ZIulL9+8CqC9Y" crossorigin="anonymous">
  <script defer src="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.js" integrity="sha384-K3vbOmF2BtaVai+Qk37uypf7VrgBubhQreNQe9aGsz9lB63dIFiQVlJbr92dw2Lx" crossorigin="anonymous"></script>
  <script defer src="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/contrib/auto-render.min.js" integrity="sha384-kmZOZB5ObwgQnS/DuDg6TScgOiWWBiVt0plIRkZCmE6rDZGrEOQeHM5PcHi+nyqe" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
  <!-- Body scroll lock for apple (issue #40) -->
  <!--script src="https://unpkg.com/body-scroll-lock"></script-->
</HEAD>
<!-------------------------------------------->
<BODY>
  <DIV CLASS="hider"> 
    <DIV CLASS="intro">
        <DIV CLASS="introBlock" STYLE="text-align:center">
          <H1>The CoffeeShop</H1>
          <p>Free ganja.js samples!</p>
          <DIV CLASS="introButton" ONCLICK="closeBar();">&times; Close</DIV>
        </DIV>
        <DIV CLASS="introBlock">
          <H3>Roll your own ganja :<H3>
          <UL>
            <LI>Browse the samples below.
            <LI>Edit them, save and share.
            <LI>Or hit the new button.
          </UL>
          <!--
          <H3>Start with a boilerplate:</H3>
          <SPAN CLASS="introButton"><SMALL>\(\mathbb R_{2,0,1}\)</SMALL> 2D PGA</SPAN>
          <SPAN CLASS="introButton"><SMALL>\(\mathbb R_{3,0,1}\)</SMALL> 3D PGA</SPAN><BR>
          <SPAN CLASS="introButton"><SMALL>\(\mathbb R_{3,1}\)</SMALL> 2D CGA</SPAN>
          <SPAN CLASS="introButton"><SMALL>\(\mathbb R_{4,1}\)</SMALL> 3D CGA</SPAN>
          -->
        </DIV>
        <DIV CLASS="introBlock">
          <H3>Links :</H3>
          <A TARGET="_BLANK" HREF="https://github.com/enkimute/ganja.js">Ganja.js on <b>github.</b></A>
          <A TARGET="_BLANK" HREF="https://www.npmjs.com/package/ganja.js">Ganja.js on <b>npm.</b></A>
          <A TARGET="_BLANK" HREF="https://beta.observablehq.com/@enkimute/ganja-js-cheat-sheets">ObservableHQ cheat-sheets.</A>
        </DIV>
        <DIV CLASS="introBlock">
          <H3>Ganja.js is for:</H3>
          <UL>
            <LI>Learning and discovering Geometric Algebra
            <LI>Testing GA Algorithms
            <LI>Visualizing GA in any Algebra
            <LI>Realtime GA in your own projects
          </UL>
        </DIV>
    </DIV>
  </DIV>
  <DIV CLASS="header">
    <H3 STYLE="flex-grow:1" ID="title">The CoffeeShop</H3>
    <DIV CLASS="introButton" ID="new"><B>&plus;</B> New</DIV>
    <DIV CLASS="introButton" ID="save"><B>&bigstar;</B> Save</DIV>
    <DIV CLASS="introButton" ID="run"><B>&rtrif;</B> Run</DIV>
  </DIV>
  <DIV CLASS="content">
    <DIV CLASS="left">
      <DIV CLASS="tabs">
        <DIV ID="tab1" CLASS="tab">free samples</DIV>
        <DIV ID="tab2" CLASS="tab">my stash</DIV>
        <DIV ID="tab3" CLASS="tab">inspect stash</DIV>
        <DIV STYLE="flex-grow:1"></DIV>
        <DIV ID="tab4" CLASS="rtab">?</DIV>
      </DIV>
      <DIV CLASS="leftc">
        <DIV CLASS="properties" ID="properties">
          <P>Stash Info</P>
          <INPUT ID="titleInput" TYPE="text" PLACEHOLDER="title goes here"></INPUT>
          <TEXTAREA ID="descriptionInput" ROWS=4 PLACEHOLDER="description goes here"></TEXTAREA>
          <P>Image Options</P>
          <INPUT ID="widthInput" TYPE="text" PLACEHOLDER="width in pixels"></INPUT>
          <INPUT ID="heightInput" TYPE="text" PLACEHOLDER="height in pixels"></INPUT>
          <INPUT TYPE="button" VALUE="download SVG/PNG" ONCLICK="saveImage()" STYLE="cursor:pointer"></INPUT>
          <P>Stash URL <span STYLE="cursor:pointer">click to copy</span></P>
          <INPUT ID="urlInput" TYPE="text" VALUE="http://"></INPUT>
          <!--
          <P>Stash Data <span>js : "data"</span></P>
          <INPUT TYPE="text" PLACEHOLDER="url to data"></INPUT>
          <DIV CLASS="dropzone">drop file here</DIV>
          -->
          <p>My stash</p>
          <DIV ID="stash"></DIV>
        </DIV>
        <DIV CLASS="sampleGroup complex">Complex   <SPAN>\(\mathbb R_{0,1} \cong \mathbb C\)</SPAN>   </DIV>     <DIV CLASS="group complex"></DIV>
        <DIV CLASS="sampleGroup dual">Dual      <SPAN>\(\mathbb R_{0,0,1} \cong \mathbb D\)</SPAN> </DIV>        <DIV CLASS="group dual"></DIV>
        <DIV CLASS="sampleGroup quaternion">Quaternion<SPAN>\(\mathbb R_{0,2} \cong \mathbb H\)</SPAN>   </DIV>  <DIV CLASS="group quaternion"></DIV>
        <DIV CLASS="sampleGroup timespace">Timespace <SPAN>\(\mathbb R_{1,3} \cong \mathbb M\)</SPAN>   </DIV>   <DIV CLASS="group timespace"></DIV>
        <DIV CLASS="sampleGroup ga3d">3D GA<SPAN>\(\mathbb R_{3}\)</SPAN>   </DIV>                               <DIV CLASS="group ga3d"></DIV>
        <DIV CLASS="sampleGroup pga2d">2D PGA    <SPAN>\(\mathbb R_{2,0,1}\)</SPAN> </DIV>                       <DIV CLASS="group pga2d"></DIV>
        <DIV CLASS="sampleGroup pga3d">3D PGA    <SPAN>\(\mathbb R_{3,0,1}\)</SPAN> </DIV>                       <DIV CLASS="group pga3d"></DIV>
        <DIV CLASS="sampleGroup cga2d">2D CGA    <SPAN>\(\mathbb R_{3,1}\)</SPAN>   </DIV>                       <DIV CLASS="group cga2d"></DIV>
        <DIV CLASS="sampleGroup cga3d">3D CGA    <SPAN>\(\mathbb R_{4,1}\)</SPAN>   </DIV>                       <DIV CLASS="group cga3d"></DIV>
        <DIV CLASS="sampleGroup mga3d">3D Mother Algebra<SPAN>\(\mathbb R_{4,4}\)</SPAN></DIV>                   <DIV CLASS="group mga3d"></DIV>
        <DIV CLASS="sampleGroup csga2d">2D CSGA   <SPAN>\(\mathbb R_{5,3}\)</SPAN>   </DIV>                      <DIV CLASS="group csga2d"></DIV>
        <DIV CLASS="sampleGroup ccga3d">3D CCGA   <SPAN>\(\mathbb R_{6,3}\)</SPAN>   </DIV>                      <DIV CLASS="group ccga3d"></DIV>
        <DIV CLASS="sampleGroup qcga3d">3D QCGA   <SPAN>\(\mathbb R_{9,6}\)</SPAN>   </DIV>                      <DIV CLASS="group qcga3d"></DIV>
        <DIV CLASS="sampleGroup c2dga">Cubic 2D GA<SPAN>\(\mathbb R_{9,7}\)</SPAN>   </DIV>                      <DIV CLASS="group c2dga"></DIV>
        <DIV CLASS="sampleGroup game">Game</DIV>                                                                 <DIV CLASS="group game"></DIV>
        <DIV CLASS="inspect">
          <INPUT TYPE="edit" ID="editJSON"></INPUT>
          <INPUT TYPE="button" ID="copyJSON" VALUE="Copy JSON to clipboard."</INPUT>
          <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
            <THEAD>
              <TR><TH WIDTH="30px">#<TH WIDTH="30px">Grade<TH>Name<TH WIDTH="80%">Value
            </THEAD>
            <TBODY ID="inspectTable">
            </TBODY>
          </TABLE>
          <P>Basis and Metric</P>
          <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
            <THEAD>
            </THEAD>
            <TBODY ID="inspectTable2">
            </TBODY>
          </TABLE>
        </DIV>
        <DIV CLASS="help">
          <p>Ganja.js supports operator overloading and algebraic literals.</p>
          <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
            <THEAD>
              <TR><TH>Operator<TH>Javascript<TH>Name
            </THEAD>
              <TR><TD>\(a*b\)<TD>a*b<TD>Geometric Product
              <TR><TD>\(a\wedge b\)<TD>a^b<TD>Outer Product
              <TR><TD>\(a\vee b\)<TD>a&b<TD>Regressive Product
              <TR><TD>\(a\cdot b\)<TD>a&lt;&lt;b<TD>Left Contraction
              <TR><TD>\(a*b*\tilde a\)<TD>a>>>b<TD>Sandwich Product
              <TR><TD>\(\tilde a\)<TD>~a<TD>Conjugate
              <TR><TD>\(\bar a\)<TD>!a<TD>Dual
              <TR><TD>\(\bar{\bar a}\)<TD>a.Reverse<TD>Reverse
              <TR><TD>\(a^{-1}\)<TD>a**-1<TD>Inverse
              <TR><TD>\(e^a\)<TD>Math.E**a<TD>Exponentiation
              <TR><TD>\(a_{\langle b \rangle}\)<TD>a.Grade(b)<TD>Grade Extraction
              <TR><TD>\(a+b\) or \(a-b\)<TD>a+b or a-b<TD>Multivector Addition/Subtraction
              <TR><TD>\(4.2e_{12}\)<TD>4.2e12<TD>Blade Literals
            <TBODY>
            </TBODY>
          </TABLE>
          <p>Ganja.js supports vectors and matrices with multivector elements.</p>
          <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
            <THEAD>
              <TR><TH>Operator<TH>Javascript<TH>Name
            </THEAD>
            <TBODY>
              <TR><TD STYLE="padding:5px">\(\bold v = 
              \begin{bmatrix} 
                e_1 & 0 
              \end{bmatrix}
              \)<TD>v = [1e1,0];<TD>Vector
              <TR><TD STYLE="padding:5px">\(\bold A = 
              \begin{bmatrix} 
                1 & 0 \\
                0 & e_{12}
              \end{bmatrix}
              \)<TD>A = [[1,0],[0,1e12]];<TD>Matrix
              <TR><TD>\(\bold v \cdot \bold w\)<TD>v*w<TD>Vector-Vector dot product.
              <TR><TD>\(A \bold v\)<TD>A*v<TD>Matrix-Vector product.
              <TR><TD>\(AB\)<TD>A*B<TD>Matrix-Matrix product.
              <TR><TD>\(A^{HT}\)<TD>~A<TD>Conjugate-Transpose Matrix
            </TBODY>
          </TABLE>
          <p>Ganja.js can graph 2D and 3D PGA and CGA elements.</p>
          <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
            <THEAD>
              <TR><TH>Element<TH>Description
            </THEAD>
            <TBODY>
              <TR><TD>multivector<TD>point, pair, line, circle, sphere, plane
              <TR><TD>number<TD>Sets color. (e.g. #80FF0000 - transparent red)
              <TR><TD>string<TD>Label for the last drawn item
              <TR><TD>[point,point]<TD>Line segment between two points
              <TR><TD>[point,point,...,point]<TD>(convex) polygon
            </TBODY>
          </TABLE>
          
        </DIV>
      </DIV>  
    </DIV>
    <DIV CLASS="gutter" ID="lgutter" TITLE="drag to size, double click to collapse"></DIV>
    <DIV CLASS="center">
      <IFRAME ID="sample" SRC="" FRAMEBORDER=0></IFRAME>
    </DIV>
    <DIV CLASS="gutter" ID="rgutter" TITLE="drag to size, double click to collapse"></DIV>
    <DIV CLASS="right">
      <PRE CLASS="source" ID="source"></PRE>
    </DIV>
  </DIV>
  
  
  <SCRIPT>
  // Data : built-in examples.
    var examples = ["complex_mandelbrot","complex_least_squares",
                    "dual_differentiation","dual_backpropagation",
                    "quaternion_hue","quaternion_mandelbrot",
                    "timespace_lorentz",
                    "ga3d_rotor_estimation",
                    "pga2d_points_and_lines","pga2d_distances_and_angles","pga2d_project_and_reject","pga2d_rotors_and_translators","pga2d_isometries", "pga2d_inverse_kinematics","pga2d_separating_axis","pga2d_pose_estimation","pga2d_euler_line","pga2d_desargues_theorem","pga2d_differentiation","pga2d_physics_moon","pga2d_origami", "pga2d_poncelet", "pga2d_non_euclidean","pga2d_raytrace",
                    "pga3d_points_and_lines","pga3d_distances_and_angles","pga3d_rotors_and_translators","pga3d_icosahedron","pga3d_sampling","pga3d_slicing","pga3d_differentiation","pga3d_skinning","pga3d_animation","pga3d_physics_planets","pga3d_origami","pga3d_physics_symmetric_top","pga3d_physics_free_top","pga3d_objects","pga3d_levenberg_marquardt","pga3d_motor_orbits",
                    "chapter11_motor_reconstruction","chapter11_motors","chapter11_tricycle",
                    "cga2d_points_and_circles","cga2d_project_and_reject","cga2d_rotors_and_translators","cga2d_euler_line","cga2d_circle_fit","cga2d_conformal","cga2d_conformal2",
                    "cga3d_points_circles_lines","cga3d_points_spheres_planes","cga3d_dual_spheres_planes","cga3d_intersections","cga3d_project_reject","cga3d_opns_visualizer","cga3d_opns_line_circle","cga3d_json",
                    "mga3d_points_and_lines",
                    "ccga3d_points_quadrics",
                    "csga2d_opns",
                    "qcga3d_points_and_more",
                    "c2dga_curves",
                    "game_wedge"];
  
  
  // Elements.
    var els={};
    [".hider", "#inspectTable", "#inspectTable2", "#editJSON", "#copyJSON", ".inspect",".help","#stash", "#tab1", "#tab2", "#tab3", "#tab4","#run", "#title", "#titleInput", "#descriptionInput", "#urlInput", "#new","#fork","#save", "#properties", "#sample", ".left",".center",".right","#lgutter","#rgutter"].forEach(x=>els[x.replace(/[.#]/g,'')]=document.querySelector(x));
  
  // Ace editor.
    if (window.ace) {
    var editor = ace.edit('source');
      editor.setTheme("ace/theme/chrome");
      editor.session.setMode("ace/mode/javascript");
      editor.session.setUseWorker(false);
      editor.setHighlightActiveLine(false);
      editor.$blockScrolling = Infinity;
      editor.session.setOptions({tabSize: 2,useSoftTabs: true});
      // Body scroll lock for iOs
//      bodyScrollLock.disableBodyScroll();

      // Sliders for selected numerical values.
      var lock = false, _slider, _color, timeout;
      editor.getSession().selection.on('changeSelection', function(e) {
        if (lock) return;
        // Get the selection plus two extra chars.
        var range = editor.getSelectionRange();
        range.end.column += 1; range.start.column -= 1;
        var t = editor.getSession().getTextRange(range);
        // Remove old slider.
        if (_slider) {
          document.getElementById('source').removeChild(_slider);
          _slider = undefined;
        } 
        if (_color) {
          document.getElementById('source').removeChild(_color);
          _color = undefined;
        }
        if (range.start.column == range.end.column-2) return;
        // If its a hex number, setup the color thingie
        var color = t.match(/^[^\d]0x([0-9a-fA-F]+)[^0-9a-fA-F]$/);
        if (color) {
          _color = document.createElement('input');
          Object.assign(_color,{type:'color', value:'#'+color[1]});
          var pos = editor.renderer.textToScreenCoordinates(range.start)
          Object.assign(_color.style, {position:'fixed', top:pos.pageY-30+'px', left:pos.pageX+5+'px'});
          document.getElementById('source').appendChild(_color);
          _color.oninput = function(e) {
            lock = true;
            var range = editor.selection.getRange();
            range.end = editor.session.replace(editor.selection.getRange(), '0x'+this.value.slice(1));
            editor.selection.setRange(range);
            lock = false;
            if (timeout) clearTimeout(timeout);
            timeout = setTimeout(()=>els.run.click(),1000/60);
          }
        }
        // If its a number, setup the slider.
        var number = t.match(/^[^\d\.\-x]([+-]{0,1}\d*\.{0,1}\d*)[^\d\.x]$/); 
        if (number) {
          _slider = document.createElement('input');
          Object.assign(_slider,{type:'range',min:-1,max:+1,step:0.01, value:number[1]*1});
          var pos = editor.renderer.textToScreenCoordinates(range.start)
          Object.assign(_slider.style, {position:'fixed', top:pos.pageY-30+'px', left:pos.pageX+5+'px'});
          document.getElementById('source').appendChild(_slider);
          _slider.oninput = function(e) {
            lock = true;
            var range = editor.selection.getRange();
            range.end = editor.session.replace(editor.selection.getRange(), this.value+'');
            editor.selection.setRange(range);
            lock = false;
            if (timeout) clearTimeout(timeout);
            timeout = setTimeout(()=>els.run.click(),1000/60);
          }
        } 
      });
    } else { // can't load editor.
      Object.assign(els.right.style,{width:0,maxWidth:0,minWidth:0});
    }

  // copy JSON to clipboard.
    els.copyJSON.onclick = e=>{
    // see if we have a canvas.
      var disp = els.sample.contentDocument.querySelector("svg")||els.sample.contentDocument.querySelector("canvas");
      if (!disp || !disp.value) return;
    // upgrade the toJSON of the elements we have.
      disp.value.some(v=>{
        if (v.Wedge) v.constructor.prototype.toJSON = function(){ return [...this]; };
        return v.Wedge !== undefined;
      });
    // next copy the value.  
      els.editJSON.value = JSON.stringify(disp.value);
      els.editJSON.focus(); els.editJSON.select(); document.execCommand("copy"); els.editJSON.blur();
    }

  // inspect
    function inspect(skip) {
       var disp = els.sample.contentDocument.querySelector("svg")||els.sample.contentDocument.querySelector("canvas");
       if (!disp || !disp.value) { els.inspectTable.innerHTML=''; return; }
       var count=1, el;
       els.inspectTable.innerHTML = disp.value.map((x,i,a)=>{ 
         while (x && x.call) x=x(); if (!x || !(x.buffer instanceof els.sample.contentWindow.ArrayBuffer)) return "";
         var name = typeof a[i+1] == "string" ? a[i+1]:"";
         var grade = Math.log2(x.length); if (grade) el=x.constructor;
         while (grade && x.Grade(grade).VLength==0) grade--;
         return `<TR ID="inspect_${i}"><TD>${count++}<TD>${grade}<TD>${name}<TD>${x.toString().replace(/\.(\d\d)\d*/g,'.$1').replace(/1.00e/g,"e").replace(/e_(\d+)/g,'e<sub>$1</sub>')}`;
       }).join('');
       if (skip!==true) disp.addEventListener("input",()=>{inspect(true);})
       var info = el && el.describe();
       els.inspectTable2.innerHTML=!info?"":[...Array(Math.log2(info.basis.length)+2)].map((x,g)=>
         `<TR>${info.metric.map((x,i)=> info.basis[i].length!=g?"": "<TD CLASS='"+["min","zer","plu"][x+1]+"'>"+ (i==0?1:"e<SUB>"+info.basis[i].replace("e","")+"</SUB>") +"<SMALL>"+(x==1?"+1":x)+"</SMALL>").join('')}`
       ).join('');
       [...els.inspectTable.querySelectorAll("tr")].forEach(e=>e.onclick=function(){
         var elid = this.id.match(/\d+$/g)[0]|0, xx = disp.value[elid]; while(xx.call)xx=xx();
         els.inspectTable2.innerHTML=!info?"":[...Array(Math.log2(info.basis.length)+2)].map((x,g)=>
           `<TR>${info.metric.map((x,i)=> info.basis[i].length!=g?"": "<TD CLASS='"+["min","zer","plu"][x+1]+"'>"+ (i==0?1:"e<SUB>"+info.basis[i].replace("e","")+"</SUB>") +"<SMALL>"+(x==1?"+1":x)+"</SMALL>").join('')}`+
           `<TR>${info.metric.map((x,i)=> info.basis[i].length!=g?"": "<TD cLASS='input'><INPUT ID='in_"+i+"' TYPE='text' vALUE='"+(xx[i]||"")+"'>").join('')}`
         ).join('');
         [...els.inspectTable2.querySelectorAll("input")].forEach(e=>e.onkeyup=function(){
           var xx = disp.value[elid]; if (xx.call) return this.style.background="red";
           var idx = this.id.match(/\d+$/g)[0];
           xx[idx] = 1*this.value;
           if (!disp.options.animate && disp.update) disp.update(disp.value);
         });
         
       });
    }

  // tabs.
    els.tab1.onclick = (e)=>{ 
      els.tab1.classList.add('active'); els.tab2.classList.remove('active'); els.tab4.classList.remove('active'); els.tab3.classList.remove('active');  
      els.properties.style.display = "";
      els.inspect.style.display = "none";
      els.help.style.display = "none";

      [...document.querySelectorAll(".group")].forEach(x=>x.style.display="block");
      [...document.querySelectorAll(".sampleGroup")].forEach(x=>{
        x.style.display="";
        if (x.classList.contains('selected')) {
          document.querySelector(".group."+x.classList[1]).style.display="block"
        }
      });
    };
    els.tab2.onclick = (e)=>{ 
      els.tab2.classList.add('active'); els.tab1.classList.remove('active'); els.tab4.classList.remove('active'); els.tab3.classList.remove('active');
      els.properties.style.display = "flex";
      els.inspect.style.display = "none";
      els.help.style.display = "none";
      [...document.querySelectorAll(".group")].forEach(x=>x.style.display="none");
      [...document.querySelectorAll(".sampleGroup")].forEach(x=>x.style.display="none");
    };
    els.tab3.onclick = (e)=>{ 
      els.tab3.classList.add('active'); els.tab1.classList.remove('active');  els.tab2.classList.remove('active');  els.tab4.classList.remove('active'); 
      els.properties.style.display = "none";
      els.inspect.style.display = "flex";
      els.help.style.display = "none";
      [...document.querySelectorAll(".group")].forEach(x=>x.style.display="none");
      [...document.querySelectorAll(".sampleGroup")].forEach(x=>x.style.display="none");
      inspect();
    };
    els.tab4.onclick = (e)=>{ 
      els.tab4.classList.add('active'); els.tab1.classList.remove('active');  els.tab2.classList.remove('active');  els.tab3.classList.remove('active'); 
      els.properties.style.display = "none";
      els.inspect.style.display = "none";
      els.help.style.display = "flex";
      [...document.querySelectorAll(".group")].forEach(x=>x.style.display="none");
      [...document.querySelectorAll(".sampleGroup")].forEach(x=>x.style.display="none");
      inspect();
    };

  // Generate example cards.
    var excards = examples.map(e=>{
      var card = Object.assign(document.createElement('div'),{className:'card'});
      if (e) card.style.backgroundImage="url('../images/"+e+".jpg')";
      card.innerHTML = '<p>'+e.split('_').slice(1).join(' ')+'</p>';
      card.onclick = (x)=>{
        els.sample.src='example_'+e+'.html';
        document.location.hash = e;
        els.title.innerText = "The CoffeeShop";
        els.titleInput.value = els.descriptionInput.value = "";
        els.urlInput.value = document.location;
        els.save.classList.remove('active');
      }  
      (document.querySelector('.group.'+e.split('_')[0])||els.left).appendChild(card);
      return card;
    });
    
  // Generate cards for my stash.
    function updateStash() {
      var list = JSON.parse(localStorage.ganja||"[]");
      while (els.stash.firstChild) els.stash.removeChild(els.stash.firstChild);
      list.forEach(e=>{
        var card = Object.assign(document.createElement('div'),{className:'card'});
        if (e.thumb) card.style.backgroundImage="url('"+e.thumb+"')";
        card.innerHTML = '<p>'+e.title+'</p>';
        card.onclick = (x)=>{
          document.location.hash = e.id;
          loadStash(false);
        }  
        var closer = Object.assign(document.createElement('div'),{className:'delete',innerHTML:'&otimes;'});
        closer.onclick = function(){
          var list = JSON.parse(localStorage.ganja||"[]").filter(x=>x.id!=e.id);
          localStorage.ganja = JSON.stringify(list);
          updateStash();
        }
        card.appendChild(closer);
        els.stash.appendChild(card);
        els.save.classList.remove('active');
      });  
    }; 
    updateStash();  
    
  // Close the introbar
    function closeBar() { document.querySelector('.hider').style.height='0px'; /*postRun();*/ editor&&editor.resize(); }  
    
  // Setup sample group handlers
    [...document.querySelectorAll(".sampleGroup")].forEach(s=>{
      s.onclick = function(){ 
        [...document.querySelectorAll(".sampleGroup")].forEach(s=>s.classList.remove('selected'));
        s.classList.toggle('selected');
        [...document.querySelectorAll(".group")].forEach(s=>{ 
          if (s.className.split(' ')[1] != this.className.split(' ')[1]) s.style.display='none'; 
          else s.style.display = 'block'; 
        });
      }
    });  
  
  // Sizeable panes.  
    els.lgutter.onmousedown = (e)=>{
      e.preventDefault(); e.stopPropagation(); els.center.style.pointerEvents = 'none'; els.left.style.overflow = 'hidden';
      window.onmousemove = function(e) { els.left.style.width = els.left.style.minWidth = els.left.style.maxWidth =  Math.max(0,e.x-2)+"px"; postRun(); };
      window.onmouseleave = window.onmouseup = function(e) { els.left.style.overflow=''; els.center.style.pointerEvents = 'all'; window.onmouseleave = window.onmouseup = window.onmousemove = undefined; };
    }
    els.rgutter.onmousedown = (e)=>{
      e.preventDefault(); e.stopPropagation(); els.center.style.pointerEvents = 'none';
      window.onmousemove = function(e) { els.right.style.width = els.right.style.minWidth = (window.innerWidth-e.x-2)+"px"; postRun(); };
      window.onmouseleave = window.onmouseup = function(e) { els.center.style.pointerEvents = 'all'; window.onmouseleave = window.onmouseup = window.onmousemove = undefined; };
    }
    els.lgutter.ondblclick = (e)=>{ els.left.style.width = els.left.style.minWidth = els.left.style.maxWidth = els.left.style.width=="0px" ? "":"0px"; postRun(); }
    els.rgutter.ondblclick = (e)=>{ els.right.style.width = els.right.style.minWidth = els.right.style.maxWidth = els.right.style.width=="0px" ? "":"0px"; postRun(); }
  
  // Link run handler.
    els.run.onclick=(e)=>{
      editor.getSession().clearAnnotations();
//      var f = els.sample.contentDocument.body.firstChild; while (f && !(f instanceof els.sample.contentWindow.HTMLScriptElement)) f = f.nextElementSibling; f = f&&f.nextElementSibling;
//      if (f) while (true) { var g=f.nextSibling; els.sample.contentDocument.body.removeChild(f); if (!g) break; f=g; }
      try { 
        //els.sample.contentWindow.eval( editor.getValue() ); 
        var script = document.createElement("script");
        script.type = "module";
        script.textContent = 'while(document.body.firstChild) document.body.removeChild(document.body.firstChild);' + editor.getValue() + '\ntop.postRun();';
        els.sample.contentDocument.body.appendChild(script);
//        setTimeout(()=>postRun(),1); // for fox.
      } catch (e) {
        var line = e.stack.toString().match(/\, <anonymous>:(\d+)/);
        if (line && line[1]) {
          line = line[1]|0;
          els.sample.contentDocument.body.innerHTML+=`<PRE>${e.message+"\n   at line : "+line}</PRE>`;
        } else {
          els.sample.contentDocument.body.innerHTML+=`<PRE>${e.message+"\n"+e.stack}</PRE>`;
        }
      }
    }  

  // helper to resize loaded content  
    function postRun(disp_in) {
    // See if we have a canvas.  
      var doc = els.sample.contentDocument||document, disp = doc.querySelector("svg")||doc.querySelector("canvas");
      if (disp && disp.value) { 
        disp.style.width=disp.style.height='100%'; 
        Object.assign(doc.body.parentElement.style,{width:'100%', height:'100%'});
        if (disp.update && (!disp.options || !disp.options.animate)) disp.update(disp.value); 
      }
    }
    
  // on iframe load..
    els.sample.onload = (e)=>{
    // setup iframe props.
      if (!els.sample.src.match(/blob/))
        els.sample.contentDocument.body.style = "margin:0; padding:0;";
      postRun();
      els.sample.contentWindow.addEventListener('error', function(e) {
        // show error in HTML
        els.sample.contentDocument.body.innerHTML+=`<PRE>${e.message}</PRE>`;
        // show error in ace editor
        var row = e.lineno;
        // e.lineno is one off in same cases... fixing that, otherwise it looks strange
        // Chrome
        if (e.message.includes("Unexpected identifier")) {
          row--;
        }
        // Mozilla
        if (e.message.includes("unexpected token: identifier")) {
          row--;
        }
        editor.getSession().setAnnotations([{
          row,
          column: e.colno,
          text: e.message,
          type: "error" // or "warning"
        }]);
      });
    // Grab the source and update the editor.  
      var scr = [...els.sample.contentDocument.querySelectorAll("script")].pop();
      if (window.editor && scr) {
        editor.setValue(scr.innerText.split('\n').slice(1).reverse().slice(1).reverse().join('\n'));
        editor.clearSelection();
      // Link shift+enter  
        editor.commands.addCommand({ name: "run changes", bindKey: {win: "Shift-Enter", mac: "Shift-Enter"}, exec: function(editor) { els.run.click(); } });
      // Link editor changes.  
        editor.session.on('change',e=>{ els.save.style.display="block"; els.save.classList.add("active"); });
        els.save.classList.remove('active');
      }
    }
    
  // Process incoming hash.
    var hashurl = document.location.hash.slice(1)||"pga2d_points_and_lines";
    document.location.hash = hashurl;

  // Propertie handlers
    els.titleInput.onkeyup = ()=>document.title = els.title.innerText = els.titleInput.value;
    
  // copy url
    els.urlInput.onclick = function(){
      console.log('copy ',this.value);
      this.select();
      document.execCommand("copy");
      this.blur();
    }  
    els.urlInput.onkeydown = (e)=>{e.preventDefault(); }; // prevent typing in URL field, keep click event working .. DISABLED wont work ..


  // External scripts.
    var lscripts = [
      document.URL.replace(/\/.[^/]*\/.[^/]*$/,'/ganja.js'),
      document.URL.replace(/\/.[^/]*\/.[^/]*$/,'/examples/coffeeshop.js'),
      'https://cdn.jsdelivr.net/gh/markedjs/marked/marked.min.js',
      'https://cdn.jsdelivr.net/npm/katex@0.15.1/dist/katex.min.js',
      'https://cdn.jsdelivr.net/npm/katex@0.15.1/dist/contrib/auto-render.min.js',
      'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.3.1/build/highlight.min.js',
      'https://cdn.jsdelivr.net/npm/in-view@0.6.1/dist/in-view.min.js',
      'https://enki.ws/GAmphetamine.js'
    ].map(x=>'<SCR'+'IPT SRC="'+x+'"></SC'+'RIPT>').join('');
    
  // Add katex stylesheet
    lscripts += '<link rel="stylesheet" href="'+document.URL.replace(/\/.[^/]*\/.[^/]*$/,'/examples/coffeeshop.css')+'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.15.1/dist/katex.min.css" integrity="sha384-R4558gYOUz8mP9YWpZJjofhk+zx0AS11p36HnD2ZKj/6JR5z27gSSULCNHIRReVs" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.3.1/build/styles/default.min.css">'  
  
  // new stash
    els.new.onclick = function newStash() {
    // Clear the editor, set the source to empty.
      editor&&editor.setValue("");
      var src = '<!DOCTYPE html><HTML><HEAD><TITLE>My Stash</TITLE>'+lscripts+'</HEAD><BODY><SC'+'RIPT>\n\n</'+'SCRIPT></BODY></HTML>';
      var blob = new Blob([src],{type:"text/html"});
      els.sample.src = URL.createObjectURL(blob);
    // Remove the samples, show the properties.  
      els.tab2.click();
    // If still in intro, close the bar.  
      closeBar();
    // Now save an empty version and set the url.
      var data = "";
      var title = "untitled";
      var packet = {title,data};
      fetch("https://ganja.enki.ws",{method:'post',body:JSON.stringify(packet)})
      .then(resp=>resp.json())
      .then(resp=>{
      // Update hash and url input
        document.location.hash=resp.id;
        els.urlInput.value=document.location;
      // Save in localstorage.
        if (window.localStorage) {
          localStorage.ganja = JSON.stringify(JSON.parse(localStorage.ganja||'[]').concat([{title,id:resp.id,secret:resp.secret}]));
        };
        els.save.style.display="block";
        els.save.classList.remove('active');
        updateStash();
      })
    };
    
  // Utility : create thumbnail.
    function createThumb(complete) {
    // See if we have an SVG or CANVAS
      var c = Object.assign(document.createElement('canvas'),{width:160,height:120}), ctx = c.getContext('2d');
      var disp = els.sample.contentDocument.querySelector("canvas");
    // if its a canvas, rescale and return base64 of jpg.
      if (disp) {
        var img = new Image();
        img.src = disp.toDataURL('image/jpeg',1.0);
        img.onload = function() {
          ctx.drawImage(img,0,0,160,120);
          return complete(c.toDataURL('image/jpeg',0.8));
        }
        return;  
      }  
    // else it's an SVG. render to canvas and return base64 of jpg.
      var disp = els.sample.contentDocument.querySelector("svg");
      if (!disp) return complete();
      var copy = disp.cloneNode(true);
      var data = (new XMLSerializer()).serializeToString(copy);
      var DOMURL = window.URL || window.webkitURL || window;
      var img = new Image();
      var svgBlob = new Blob([data], {type: "image/svg+xml;charset=utf-8"});
      var url = DOMURL.createObjectURL(svgBlob);
      img.src = url;
      img.onload = function () {
        ctx.drawImage(img, 0, 0,160,120);
        return complete(c.toDataURL('image/jpeg',0.8));
      };
    }  
    
  // save stash
    els.save.onclick = function saveStash() {
      els.save.classList.remove('active');
    // create thumbnail, then save ..
      createThumb((thumb)=>{
      // save .. 
        var id = document.location.hash.slice(1);
        var list = JSON.parse(localStorage.ganja||"[]");
        var item = list.filter(x=>{ if (x.id==id) { x.thumb=thumb; x.title=els.titleInput.value||"untitled"; x.description=els.descriptionInput.value; return true; } return false; });
        //if (item.length==0) return console.warn("saving disabled - not your stash ?");
        localStorage.ganja = JSON.stringify(list);
        updateStash(); 
        var packet = { data : editor.getValue(), title:els.titleInput.value||"untitled", description:els.descriptionInput.value, secret:item[0]&&item[0].secret, id : id, thumb: thumb };
        fetch("https://ganja.enki.ws",{method:'post',body:JSON.stringify(packet)})
        .then(resp=>resp.json())
        .then(resp=>{
          console.log(JSON.stringify(resp));
          if (item.length==0) { // we didn't have it .. so we forked it .. 
            var n = { id : resp.id, secret : resp.secret, thumb : thumb,  title:els.titleInput.value||"untitled", description:els.descriptionInput.value };
            list.push(n);
            localStorage.ganja = JSON.stringify(list);
            updateStash();
            document.location.hash = resp.id; 
            els.tab2.click();
          }
        })
      });
    }    
  // warn before exit
    window.onbeforeunload = (e)=>{
      if(!els.save.classList.contains('active')) return;
      e.preventDefault(); // firefox
      e.returnValue = ''; // chrome
    };
    
  // Respond to drag/drop and monitor files for updates. (allows you to store your content lcoally)  
    document.body.ondragover = (e)=>{ e.preventDefault(); return false; };
    document.body.ondrop = (e)=>{
      e.preventDefault();

     // get the file as an fileEntry (aka file handle)
      const fileEntry = e.dataTransfer.items[0].webkitGetAsEntry()
      let lastModificationTime = 0
      
      async function read (file) {
        // use the new async read method on blobs.
        editor.setValue(await file.text());
        editor.clearSelection();
        els.left.style.with=els.left.style.minWidth=els.left.style.maxWidth=0;
        els.right.style.with=els.right.style.minWidth=els.right.style.maxWidth=0        
        closeBar();
        els.run.click();
      }
      
      function compare (meta) {
        if (meta.modificationTime > lastModificationTime) {
          lastModificationTime = meta.modificationTime
          fileEntry.file(read)
        }
      }
      
      var stashURL = document.location.hash.slice(1).split('&');
      if (!JSON.parse(localStorage.ganja).find(x=>x.url=stashURL)) els.new.click();
      
      setTimeout(()=>{
        els.title.innerHTML=`monitoring "${ fileEntry.name }". Save your file locally to update.`
        setInterval(fileEntry.getMetadata.bind(fileEntry, compare),1000);
      },1000);
    }
    
  // load custom stash
    function loadStash(closeAll){
      var stashURL = document.location.hash.slice(1).split('&');
      fetch("https://ganja.enki.ws/"+stashURL[0]).then(x=>x.json()).then(data=>{
        var src = '<!DOCTYPE html><HTML><HEAD><TITLE>'+data.title+'</TITLE><STYLE>body {width:100%; height:100%; margin:0; }</STYLE>'+lscripts+'</HEAD><BODY><SC'+'RIPT>try{\n'+data.data+'\n; disp=document.querySelector("svg")||document.querySelector("canvas"); if (disp && disp.value) { disp.style.width=disp.style.height="100%"; if (disp.update && (!disp.options || !disp.options.animate)) disp.update(disp.value); } } catch(e) { document.body.innerHTML+="<PRE>"+e+"</PRE>"; }</'+'SCRIPT></BODY></HTML>';
        document.body.style.display='flex';
      // if full-screen, overload everything .. 
        if (stashURL.find(x=>x.match(/fullscreen/i))) return document.clear(),document.write(src),document.close();
      // else put it in the iframe.  
        var blob = new Blob([src],{type:"text/html"});
        var url = URL.createObjectURL(blob);
        els.sample.src = url;
        els.hider.style.height=0;
        if (closeAll!==false) els.left.style.with=els.left.style.minWidth=els.left.style.maxWidth=0;
      // Set properties.
        els.title.innerText=els.titleInput.value=data.title;
        els.descriptionInput.value=data.description||"";
        els.urlInput.value=document.location;
      // Remove the samples, show the properties.  
        els.properties.style.display = "flex";
        [...document.querySelectorAll(".group")].forEach(x=>x.style.display="none");
        [...document.querySelectorAll(".sampleGroup")].forEach(x=>x.style.display="none");
      // If still in intro, close the bar.  
        if (closeAll!==false) closeBar();
      // If its ours, we can save it..
        var id = document.location.hash.slice(1), mine = JSON.parse(localStorage.ganja||"[]").filter(x=>x.id==id).length;
        if (mine) els.save.style.display="block";
        els.tab2.click();
      });
    
    }  
    
  // Premade examples.   
    if (~examples.indexOf(hashurl.split('&')[0])) {
      els.sample.src = 'example_'+hashurl.split('&')[0]+'.html';
      els.tab1.click();
      document.body.style.display='flex';
      if (hashurl.split('&')[1] == 'fullscreen') {
        els.left.style.with=els.left.style.minWidth=els.left.style.maxWidth=0;
        els.right.style.width = els.right.style.minWidth = els.right.style.maxWidth = 0;
        [...document.querySelectorAll(".group")].forEach(x=>x.style.display="none");
        [...document.querySelectorAll(".sampleGroup")].forEach(x=>x.style.display="none");
        closeBar();
      }
    }
    
  // User shared.
    else loadStash();
    
  // image options
    function saveImage() {
      var imgC = els.sample.contentDocument.querySelector('canvas');
      var imgS = els.sample.contentDocument.querySelector('svg');
      if (imgS) {
        imgS.style.width  =  (widthInput.value|0)||'100%';
        imgS.style.height =  (heightInput.value|0)||'100%';
        var a = Object.assign(els.sample.contentDocument.createElement('a'),{
                  download:"image.svg",
                  href: 'data:image/svg+xml;utf8,' + new XMLSerializer()
                        .serializeToString(imgS)
                        .replace(/ +\/\/.*?\n/g,'')
                        .replace(/onmousedown=".*?"/g,'')
                        .replace(/#/g,'%23')
                });
        els.sample.contentDocument.body.appendChild(a).click(); els.sample.contentDocument.body.removeChild(a);
        imgS.style.width  =  '100%';
        imgS.style.height =  '100%';
      }
      if (imgC) {
        imgC.width = imgC.style.width  =  (widthInput.value|0)||'100%';
        imgC.height = imgC.style.height =  (heightInput.value|0)||'100%';
        imgC.update(imgC.value);
          var a = Object.assign(els.sample.contentDocument.createElement('a'),{
                    download:"image.png",
                    href: imgC.toDataURL('image/png',1.0)
                  });
          els.sample.contentDocument.body.appendChild(a).click(); els.sample.contentDocument.body.removeChild(a);
          imgC.style.width  =  '100%';
          imgC.style.height =  '100%';
      }
    }
      
    
      
  </SCRIPT>
</BODY>
